/*********************************************************
          File Name:16.12.h
          Author: Abby Cin
          Mail: abbytsing@gmail.com
          Created Time: Sat 12 Dec 2015 09:24:58 PM CST
**********************************************************/

#ifndef EX_16_12_H_
#define EX_16_12_H_

#include <iostream>
#include <vector>
#include <string>
#include <memory>

template<typename T>
class Blob
{
        public:
                typedef T value_type;
                // `std::vector<T>::size_type` is a dependent type name 
                // and it's NOT a type, we must a `typename` here to specify
                // that it refers to a type, otherwisr it is assumend to refer
                // to a non-type
                // here is a definition of `dependent type`:
                // http://en.cppreference.com/w/cpp/language/dependent_name
                // C++ Primer 5th (Chinese) p593
                typedef typename std::vector<T>::size_type size_type;

                Blob();
                Blob(std::initializer_list<T> il);
                // answer 16.24
                template<typename Iterator> Blob(Iterator beg, Iterator end);

                size_type size() const { return data->size(); }
                
                bool empty() const { return data->empty(); }

                void push_back(const T &t) { data->push_back(t); }

                void pop_back();

                T &back();

                // promise won't modify `data` in this function 
                // and its return value 
                const T &back() const;

                T &operator[](size_type i);

                // promise won't modify `data` in this function
                // and its return vlaue
                const T &operator[](size_type i) const;

        private:
                std::shared_ptr<std::vector<T>> data;

                void check(size_type i, const std::string &msg) const;
};

template<typename T> 
Blob<T>::Blob()
{
        data = std::make_shared(std::vector<T>());
}

template<typename T>
Blob<T>::Blob(std::initializer_list<T> il)
{
        data = std::make_shared(std::vector<T>(il));
        // or
        //data = std::make_shared(std::vector<T>(il.begin(), il.end()));
}

// answer 16.24
template<typename T>    // class template
template<typename Iterator>     // member template
Blob<T>::Blob(Iterator beg, Iterator end)
{
        data = std::make_shared(std::vector<T>(beg, end));
}

template<typename T>
inline void Blob<T>::check(size_type i, const std::string &msg) const
{
        if(i >= data->size())
                throw std::out_of_range(msg);
}

template<typename T>
inline void Blob<T>::pop_back()
{
        data->pop_back();
}

template<typename T>
inline T &Blob<T>::back()
{
        return data->back();
}

template<typename T>
inline const T &Blob<T>::back() const
{
        return data->back();
}

template<typename T>
inline T &Blob<T>::operator[](size_type i)
{
        check(i, "index out of range");
        return *(data[i]);
}

template<typename T>
inline const T &Blob<T>::operator[](size_type i) const
{
        check(i, "index out of range");
        return *(data[i]);
}

// delcaration of `BlobPtr`
template<typename> class BlobPtr;

// setup friendship between `Blob` and `BlobPtr`
template<typename T>
bool operator==(const BlobPtr<T>&, const BlobPtr<T> &);

template<typename T>
bool operator<(const BlobPtr<T> &, const BlobPtr<T> &);

// define the body of `BlobPtr`
template<typename T>
class BlobPtr
{
        public:
                BlobPtr() : curr(0) {}
                BlobPtr(Blob<T> &a, size_t sz = 0) :
                        wptr(a.data), curr(sz) {  }

                T &operator*() const
                {
                        auto p = check(curr, "dereference past end");
                        return (*p)[curr];
                }

                friend bool operator==<T>(const BlobPtr<T> &, const BlobPtr<T> &);

                // can't be `operator<<T>` ...
                friend bool operator< <T>(const BlobPtr<T> &, const BlobPtr<T> &);

                BlobPtr &operator++();

                BlobPtr &operator--();

                // postfix `i--`
                BlobPtr &operator++(int);

                BlobPtr &operator--(int);

        private:
                std::shared_ptr<std::vector<T>>
                        check(std::size_t, const std::string &) const;

                std::weak_ptr<std::vector<T>> wptr;

                std::size_t curr;
};

template<typename T>
bool operator==(const BlobPtr<T> &left, const BlobPtr<T> &right)
{
        if(left.wptr.lock() != right.wptr.lock())
                throw std::runtime_error("Points to different type of Blobs");
        return left.curr == right.curr;
}

template<typename T>
bool operator<(const BlobPtr<T> &left, const BlobPtr<T> &right)
{
        if(left.wptr.lock() != right.wptr.lock())
                throw std::runtime_error("Points to diffent type of Blobs");
        return left.curr < right.curr;
}

// id +
template<typename T>
BlobPtr<T> &BlobPtr<T>::operator++()
{
        ++curr;
        std::cout << "call id +" << std::endl;
        return *this;
}

// id -
template<typename T>
BlobPtr<T> &BlobPtr<T>::operator--()
{
        --curr;
        return *this;
}

// call `id +`
template<typename T>
BlobPtr<T> &BlobPtr<T>::operator++(int)
{
        BlobPtr res = *this;

        ++*this;

        return res;
}

// call `id -`
template<typename T>
BlobPtr<T> &BlobPtr<T>::operator--(int)
{
        BlobPtr res = *this;

        --*this;

        return res;
}

#endif
